/**
 * @file main_tester.cpp Hlavný program pre testovanie algoritmov na riešenie ECDLP (sekvenčne).
 *
 * @author Martin Kosorinský.
 * @date 23.10.2009.
 */

#include "CTimer.hpp"
#include "CLambdaAlg.hpp"
#include "CRhoTabAlg.hpp"
#include "CRhoFloydAlg.hpp"
#include "CRhoBrentAlg.hpp"
#include "CCmdLineParser.hpp"

using namespace std;

/**
 * @brief Na štandardný výstup (stdout) vypíše základné informácie o použití programu a jeho parametroch.
 */
void printUsage();
/**
 * @brief Na základe vstupných parametrov -x, -y a -i nastaví bod, ktorého diskrétny logaritmus hľadáme.
 * Procedúra na základe vstupných parametrov inicializuje bod #CECContext::H. O súradniciach, ako aj index bodu sa
 * predpokladá, že sú zadané v desiatkovej sústave. V prípade nekompletného zadania súradníc a chýbajúceho
 * parametra -i je bod H inicializovaný náhodne.
 * @warning V prípade chybného formátu vstupu je program ukončený s chybovým hlásením knižnice miracl.
 * @param[in] cm Inicializovaný objekt obsahujúci vstupné parametre programu.
 */
void setPointH(CCmdLineParser & cm);
/**
 * @brief Vstupná funkcia ukážkového programu.
 * Program načíta parametre krivky z vopred zvoleného súboru a pomocou všetkých implementovaných
 * algoritmov vypočíta diskrétny logaritmus náhodne zvoleného bodu z grupy bodov eliptickej krivky.
 * @param argc Počet vstupných argumentov programu.
 * @param argv Pole vstupných argumentov.
 */
int main(int argc, char* argv[]) {

    // spracovanie vstupnych parametrov programu
    CCmdLineParser cm(argc, argv, (char*)"he:f:d:i:x:y:BIFTL");

    if (cm.isSet('h')) {
        printUsage();
        exit(EXIT_FAILURE);
    }

    if (!cm.isSet('e')) {
        cerr << "Error: '" << __FUNCTION__ << "': Chybaju povinny parameter programu '-e'!" << endl;
        printUsage();
        exit(EXIT_FAILURE);
    }

    // dopocitame hodnoty nezadanych  parametrov
    int mirPrecis = 10;
    if (cm.isSet('d')) {
        mirPrecis = atoi(cm.getValue('d'));
    }

    // inicializacia krivky a kniznice miracl -> pred jej volanim nesmie byt
    // ziadna alokacia premennych big alebo epoint!!!

    ifstream inputFile(cm.getValue('e'));
    CECContext::init(inputFile, MR_AFFINE, mirPrecis);
    inputFile.close();

    // alokacia pamäte pre výsledky vypoctov
    big vysledok = mirvar(0);

    big A = mirvar(0);
    big B = mirvar(0);
    big p = mirvar(0);
    big ord = mirvar(0);
    epoint * G = epoint_init();
    epoint * H = epoint_init();

    // na zaklade vstupnych parametrov nastavime bod H (suradnice bodu, ktoreho DL hladame)
    setPointH(cm);

    CECContext::getParams(A, B, p, ord, G);
    CECContext::getPoint(H);

    int distFilter = 12;
    if (cm.isSet('f')) {
        distFilter = atoi(cm.getValue('f'));
        if (distFilter < 1) {
            cerr << "Error: '" << __FUNCTION__ << "': Parameter filtrovacej funkcie musi byt kladne cele cislo (max=" << INT_MAX << ")!" << endl;
            exit(EXIT_FAILURE);
        }
    }

    cout << "Parametre eliptickej krivky:" << endl;
    cout << "a   = " << A << " (" << logb2(A) << " bits)\n";
    cout << "b   = " << B << " (" << logb2(B) << " bits)\n";
    cout << "p   = " << p << " (" << logb2(p) << " bits)\n";
    cout << "ord = " << ord << " (" << logb2(ord) << " bits)" << endl;
    cout << "G   = " << G << endl;
    cout << "H   = " << H << endl;
    cout << "-------------------------------------------------------------------------------" << endl;
    cout << "Presnost (MIRACL): " << mirPrecis  << endl;
    cout << "Parameter filtracie bodov: " << distFilter << endl;
    cout << "-------------------------------------------------------------------------------" << endl;

    CTimer stopky;

    if (cm.isSet('B') || (!cm.isSet('I') && !cm.isSet('F') && !cm.isSet('T') && !cm.isSet('L'))) {
        stopky.start();
        if (CRhoBrentAlg::computeLogarithm(vysledok)) {
            cout << "Vysledok = " << vysledok << ((CRhoAlgCommon::checkIndex(vysledok)) ?  " OK" : " FAIL") << endl;
            cout << "Vypocet Pollard-Rho (brent) algoritmom trval: " << stopky.getTime() << " sekund" << endl;
        }
        else {
            cerr << "Vypocet zlyhal! Trvalo to " << stopky.getTime() << " sekund" << endl;
            //exit(EXIT_FAILURE);
        }
        cout << "-------------------------------------------------------------------------------" << endl;
    }
    if (cm.isSet('I')) {
        stopky.start();
        if (CRhoBrentAlg::computeLogarithm(vysledok, true)) {
            cout << "Vysledok = " << vysledok << ((CRhoAlgCommon::checkIndex(vysledok)) ?  " OK" : " FAIL") << endl;
            cout << "Vypocet Pollard-Rho (improved brent) algoritmom trval: " << stopky.getTime() << " sekund" << endl;
        }
        else {
            cerr << "Vypocet zlyhal! Trvalo to " << stopky.getTime() << " sekund" << endl;
            //exit(EXIT_FAILURE);
        }
        cout << "-------------------------------------------------------------------------------" << endl;
    }
    if (cm.isSet('F')) {
        stopky.start();
        if (CRhoFloydAlg::computeLogarithm(vysledok)) {
            cout << "Vysledok = " << vysledok << ((CRhoAlgCommon::checkIndex(vysledok)) ?  " OK" : " FAIL") << endl;
            cout << "Vypocet Pollard-Rho (floyd) algoritmom trval: " << stopky.getTime() << " sekund" << endl;
        }
        else {
            cerr << "Vypocet zlyhal! Trvalo to " << stopky.getTime() << " sekund" << endl;
            //exit(EXIT_FAILURE);
        }
        cout << "-------------------------------------------------------------------------------" << endl;
    }
    if (cm.isSet('T')) {
        stopky.start();
        if (CRhoTabAlg::computeLogarithm(vysledok, distFilter)) {
            cout << "Vysledok = " << vysledok << ((CRhoAlgCommon::checkIndex(vysledok)) ?  " OK" : " FAIL") << endl;
            cout << "Vypocet Pollard-Rho (tab) algoritmom trval: " << stopky.getTime() << " sekund" << endl;
        }
        else {
            cerr << "Vypocet zlyhal! Trvalo to " << stopky.getTime() << " sekund" << endl;
            //exit(EXIT_FAILURE);
        }
        cout << "-------------------------------------------------------------------------------" << endl;
    }
    if (cm.isSet('L')) {
        stopky.start();
        if (CLambdaAlg::computeLogarithm(vysledok)) {
            cout << "Vysledok = " << vysledok << ((CRhoAlgCommon::checkIndex(vysledok)) ?  " OK" : " FAIL") << endl;
            cout << "Vypocet Pollard-Lambda algoritmom trval: " << stopky.getTime() << " sekund" << endl;
        }
        else {
            cerr << "Vypocet zlyhal! Trvalo to " << stopky.getTime() << " sekund" << endl;
            //exit(EXIT_FAILURE);
        }
        cout << "-------------------------------------------------------------------------------" << endl;
    }
    // dealokácia prostriedkov a kniznice miracl
    CECContext::free();

    mirkill(vysledok);
    mirkill(A);
    mirkill(B);
    mirkill(p);
    mirkill(ord);
    epoint_free(G);
    epoint_free(H);

    return 0;
}

void setPointH(CCmdLineParser & cm) {

    epoint * H = epoint_init();
    epoint * G = epoint_init();

    miracl *mip = get_mip();
    mip->IOBASE  = 10;

    CECContext::getGenerator(G);

    if (cm.isSet('i')) {    // neznamy bod bol dany exponentom vzhladom na generator G
        big i = mirvar(0);
        cinstr(i, cm.getValue('i'));
        ecurve_mult(i, G, H);
        CECContext::setPoint(H);
        mirkill(i);
    }
    else if (cm.isSet('x') && cm.isSet('y'))  {   // bod zadany affinymi suradnicami
        big x = mirvar(0);
        big y = mirvar(0);
        cinstr(x, cm.getValue('x'));
        cinstr(y, cm.getValue('y'));
        if (!epoint_set(x, y, 0, H)) {
            cerr << "Error: '" << __FUNCTION__ << "': Bod so vstupnymi suradnicami nelezi na krivke!" << endl;
            exit(EXIT_FAILURE);
        }
        CECContext::setPoint(H);
        mirkill(x);
        mirkill(y);
    }
    else if (cm.isSet('x') || cm.isSet('y')) {  // pripad zadania len jednej suradnice
        cerr << "Error: '" << __FUNCTION__ << "': Je potrebne zadat obe affinne suradnice bodu!" << endl;
        exit(EXIT_FAILURE);
    }
    else {  // pripad, ked neni zadany ani jeden z prepinacov -x, -y a -i
        CECContext::setPoint(); // budeme hladat nahodny bod
    }

    epoint_free(H);
    epoint_free(G);
}

void printUsage() {
    cout << "Testovaci program pre vypocet diskretneho logaritmu. Parametre oznacene (*) su povinne." << endl;
    cout << "Pouzitie: ecdlp_tester [-hBIFTL] -e <path> -d <digits>" << endl;
    cout << "\t-h            :  Zobrazenie zakladnych informacii o programe" << endl;
    cout << "\t-e <string>   :  Plna cesta k ecs suboru s parametrami krivky (*)" << endl;
    cout << "\t-d <int>      :  Presnost kniznice miracl (number of digits)" << endl;
    cout << "\t-f <int>      :  Parameter filtrovacej funkcie (distinguished points)" << endl;
    cout << "\t-x <big int>  :  Prva afinna suradnica bodu H" << endl;
    cout << "\t-y <big int>  :  Druha afinna suradnica bodu H" << endl;
    cout << "\t-i <big int>  :  Exponent bodu vzhladom na zadany generator" << endl;
    cout << "\t-B            :  Bude spusteny Brentov algoritmus" << endl;
    cout << "\t-I            :  Pouzije sa vylepsený Brentov algoritmus" << endl;
    cout << "\t-F            :  Floydov algoritmus" << endl;
    cout << "\t-T            :  Vypocet Tabulkova detekcia kolízii" << endl;
    cout << "\t-L            :  Lambda algoritmus" << endl;
}
